/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


/*************************************************************************
 * File: epoll.cpp
 *************************************************************************
 *\author: starlove
 *************************************************************************/


#ifndef __MLSE_PLATFORM_WIN__

#include <network/epoll.h>
#include <network/event_epoll.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/tcp.h>

volatile int g_nSocketCount = 0;

bool CEpollNetwork::Init()
{
    return true;
}

bool CEpollNetwork::Destory()
{
    return true;
}

SOCKET CEpollNetwork::CreateTCPSocket()
{
    SOCKET s = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
    if ( s != INVALID_SOCKET )
    {
        g_nSocketCount ++;
    }
    return s;
}

void CEpollNetwork::CloseSocket( SOCKET s )
{
    if ( s != INVALID_SOCKET )
    {
        g_nSocketCount --;
    }
    close(s);
}

sockaddr *CEpollNetwork::BuildSockAddr( const char *szAddr, unsigned int uPort )
{
    static sockaddr_in sockAddr;
    memset(&sockAddr,0,sizeof(sockaddr_in));
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_addr.s_addr = inet_addr(szAddr);
    sockAddr.sin_port = htons(uPort);
    return (sockaddr *)&sockAddr;
}

void CEpollNetwork::SetSocketOpt( SOCKET s, int linger_flag )
{
    //设置socket的各类特性，在某些特殊环境里需要
    int opts = fcntl(s, F_GETFL);
    opts |= O_NONBLOCK;
    fcntl(s, F_SETFL, opts);

    struct linger ling;
    ling.l_onoff = linger_flag;
    ling.l_linger = 0;
    setsockopt( s, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling));

    int bNoDelay = 1;
    setsockopt( s, IPPROTO_TCP, TCP_NODELAY, (char*)&bNoDelay, sizeof(int));
}

unsigned int CEpollNetwork::GetSocketCount()
{
    return (unsigned int)g_nSocketCount;
}

CEpollSocket::CEpollSocket()
{
    m_ep = NULL;
    m_pp = NULL;
}

CEpollSocket::~CEpollSocket()
{
}

bool CEpollSocket::Init(CEventDispatcher *ep)
{
    m_ep = ep;
    return true;
}

void CEpollSocket::BindProcessor(IEventProcessor *pp)
{
    m_pp = pp;
}

bool CEpollSocket::Destory()
{
    return true;
}

void CEpollSocket::Close(SOCKET s)
{
	//DEBUG_INFO_NETWORK(s, "Close socket");
    CEpollNetwork::CloseSocket(s);
}

void CEpollSocket::Close(LPEVENT ev)
{
}

void CEpollSocket::BindIO(SOCKET s)
{
    CEpollNetwork::SetSocketOpt(s);
    m_ep->Bind((EVHANDLE)s);
}

void CEpollSocket::GetSockAddr(LPEVENT ev, sockaddr *pSelfAddr, sockaddr *pRemoteAddr)
{
    if(!ev)
        return;
    socklen_t st = sizeof(sockaddr_in);
    getpeername((SOCKET)ev->hd, pRemoteAddr, &st);
    getsockname((SOCKET)ev->hd, pSelfAddr, &st);
}

SOCKET CEpollSocket::Connect( const char *szAddr, unsigned int uPort )
{
    SOCKET s = CEpollNetwork::CreateTCPSocket();
    if (s == INVALID_SOCKET)
    {
        //DEBUG_INFO_NETWORK(s, "Create client socket fail!");
        return INVALID_SOCKET;
    }
    if (0 != ::connect(s, (sockaddr*)CEpollNetwork::BuildSockAddr(szAddr, uPort), sizeof(sockaddr_in)))
    {
        //DEBUG_INFO_NETWORK(s, "Connect to %s:%d fail! code: %d",szAddr, uPort, errno);
        close(s);
        return INVALID_SOCKET;
    }

    if(m_ep)
    {
        m_ep->Bind((EVHANDLE)s);
    }

    return s;
}

SOCKET CEpollSocket::Listen( unsigned int uPort, const char *szAddr /* = NULL */ )
{
    if (!szAddr)
        szAddr = "0.0.0.0";

    SOCKET s = CEpollNetwork::CreateTCPSocket();
    if (s == INVALID_SOCKET)
    {
        DEBUG_INFO_NETWORK(s, "Create listen socket fail!");
        return INVALID_SOCKET;
    }
    CEpollNetwork::SetSocketOpt(s, 1);

    int nRet = ::bind(s, (sockaddr *)CEpollNetwork::BuildSockAddr(szAddr, uPort), sizeof(sockaddr_in));
    if (nRet == -1)
    {
        if( errno == EADDRINUSE )
        {
            DEBUG_INFO_NETWORK(s, "Bind fail, address inuse!");
            return INVALID_SOCKET;
        }
        else
        {
            DEBUG_INFO_NETWORK(s, "Bind fail!");
            return INVALID_SOCKET;
        }
    }
    nRet = ::listen(s, SOMAXCONN);
    if (nRet == -1)
    {
        if( errno == EADDRINUSE )
        {
            DEBUG_INFO_NETWORK(s, "Listen fail, address inuse!");
            return INVALID_SOCKET;
        }
        else
        {
            DEBUG_INFO_NETWORK(s, "Listen fail!");
            return INVALID_SOCKET;
        }
    }
    if(m_ep)
    {
        SOCKET s_listen = s;
        LPEVENT ev = Create_Event();
        if(!ev)	return INVALID_SOCKET;
        epoll_event epollev;
        epollev.events = EPOLLIN | EPOLLOUT | EPOLLET | EPOLLONESHOT;
        epollev.data.fd = s_listen;
        epollev.data.ptr = ev;
        ev->events = ETP_UNKNOWN | ETP_NEW;
        ev->op = m_pp;
        ev->hd = (EVHANDLE)s_listen;
        epoll_ctl(m_ep->GetIO()->GetIOHandle(),EPOLL_CTL_ADD, s_listen, &epollev);
    }

    DEBUG_INFO_NETWORK(s, "listen complete!");
    return s;
}

void CEpollSocket::Accept(SOCKET s_listen, IEventProcessor *pp)
{
    if(m_ep)
    {
        LPEVENT ev = Create_Event();
        if(!ev)	return;
        epoll_event epollev;
        epollev.events = EPOLLIN | EPOLLOUT | EPOLLET | EPOLLONESHOT;
        epollev.data.fd = s_listen;
        epollev.data.ptr = ev;
        ev->events = ETP_UNKNOWN | ETP_NEW;
        ev->op = m_pp;
        ev->hd = (EVHANDLE)s_listen;
        int er = epoll_ctl(m_ep->GetIO()->GetIOHandle(),EPOLL_CTL_MOD, s_listen, &epollev);
        if (0 != er)
        {
        	if (errno == ENOENT)
        	{
				er = epoll_ctl(m_ep->GetIO()->GetIOHandle(),EPOLL_CTL_ADD, s_listen, &epollev);
				if(0 == er)
					return;
        	}
			DEBUG_INFO_NETWORK(s_listen, "IOAccept epoll_ctl fail, errno: %d", errno);
			return;
        }
    }
}

int CEpollSocket::Send(SOCKET &s, void *pData, unsigned int nSize)
{
	int r = ::send(s, (char *)pData, nSize, 0);
	if(r < 0)
	{
		int err = errno;
		if(err != 0 && err != EAGAIN && err != EWOULDBLOCK )
		{
			s = INVALID_SOCKET;
			DEBUG_INFO_NETWORK(s, "send data fail! code: %d", err);
		}
	}else
	{
		//DEBUG_INFO_NETWORK(s, "send data suscess! len: %d", r);
	}
	return r;
}

int CEpollSocket::Recv(SOCKET &s, void *pBuffer, unsigned int nBufferSize)
{
	int r = ::recv(s, (char *)pBuffer, nBufferSize, 0);
	if(r < 0)
	{
		int err = errno;
		if(err != 0 && err != EAGAIN && err != EWOULDBLOCK )
		{
			s = INVALID_SOCKET;
			DEBUG_INFO_NETWORK(s, "recv data fail! code: %d", err);
		}
	}
	return r;
}

int CEpollSocket::IOSend(SOCKET s, IPeer *pPeer, IEventProcessor *pp /* = NULL */)
{
    int er = -1;
    if(m_ep && pPeer)
    {
        LPEVENT ev = Create_Event();
        if(!ev)
        {
			//DEBUG_INFO_NETWORK(pPeer->GetIOHandle(), "Fail to create event for IOSend");
			pPeer->OnError();
			return er;
        }
        epoll_event epollev;
        epollev.events = EPOLLOUT | EPOLLET | EPOLLHUP | EPOLLONESHOT;
        epollev.data.fd = pPeer->GetIOHandle();
        epollev.data.ptr = ev;

        ev->events = ETP_UNKNOWN | ETP_WRITE_READY;
        ev->hd = (EVHANDLE)pPeer->GetIOHandle();
        ev->op = pp;
        ev->data = pPeer;
        er = epoll_ctl(m_ep->GetIO()->GetIOHandle(),EPOLL_CTL_MOD, pPeer->GetIOHandle(), &epollev);
        if (0 != er)
        {
        	if (errno == ENOENT)
        	{
				er = epoll_ctl(m_ep->GetIO()->GetIOHandle(),EPOLL_CTL_ADD, pPeer->GetIOHandle(), &epollev);
				if(0 == er)
				{
					return 0;
				}
        	}
			DEBUG_INFO_NETWORK(pPeer->GetIOHandle(), "IOSend epoll_ctl fail, errno: %d", errno);

			pPeer->OnError();
			return er;
        }
    }
    return er;
}

int CEpollSocket::IORecv(SOCKET s, IPeer *pPeer, IEventProcessor *pp /* = NULL */)
{
    int er = -1;
    if(m_ep && pPeer)
    {
        LPEVENT ev = Create_Event();
        if(!ev)
        {
			//DEBUG_INFO_NETWORK(pPeer->GetIOHandle(), "Fail to create event for IORecv");
			pPeer->OnError();
			return er;
        }
        epoll_event epollev;
        epollev.events = EPOLLIN | EPOLLET | EPOLLHUP | EPOLLONESHOT;
        epollev.data.fd = pPeer->GetIOHandle();
        epollev.data.ptr = ev;

        ev->events = ETP_UNKNOWN | ETP_READ_READY;
        ev->hd = (EVHANDLE)pPeer->GetIOHandle();
        ev->op = pPeer;
        ev->data = pPeer;
        er = epoll_ctl(m_ep->GetIO()->GetIOHandle(),EPOLL_CTL_MOD, pPeer->GetIOHandle(), &epollev);
        //printf("register epoll event for %d, code: %d\n", pPeer->GetIOHandle(), errno);
        if (0 != er)
        {
        	if (errno == ENOENT)
        	{
				er = epoll_ctl(m_ep->GetIO()->GetIOHandle(),EPOLL_CTL_ADD, pPeer->GetIOHandle(), &epollev);
				if(0 == er)
					return 0;
        	}
			//DEBUG_INFO_NETWORK(pPeer->GetIOHandle(), "IORecv epoll_ctl fail, errno: %d", errno);
			pPeer->OnError();
			return er;
        }
    }
    return er;
}
#endif
